package com.example.framework.web.annotation.aspect;

import com.example.framework.common.pojo.R;
import com.example.framework.common.util.json.JsonUtils;
import com.example.framework.web.annotation.LogRequest;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.UUID;

/**
 * @author yuzhenxin
 * @date 2023/3/28
 */
@Slf4j
@Aspect
public class LogRequestAspect {

    // 记录每个线程的处理开始时间
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    // 记录每个线程的id
    ThreadLocal<String> reqUUID = new ThreadLocal<>();

    /**
     * 1. execution，指定方法
     * 修饰符（非必填） 返回值类型 全包名.类名（非必填）.方法名(参数类型)
     * public * com.example..*.controller..*(..)
     * <p>
     * 2. within 指定范围
     * com.example.service..*
     * com.example.service.Person+
     * <p>
     * 3. @within
     * 目标类(target)如果有{@link org.springframework.transaction.annotation.Transactional}注解的所有方法（只需要在类上标注即可切面所有方法）
     * <p>
     * 4. @annotation
     * 任何方法有{@link org.springframework.transaction.annotation.Transactional}注解的方法（需要每个方法加注解）
     * <p>
     * 合并切点，匹配带有 @LogRequest 注解的方法或在其类上带有 @LogRequest 注解的方法
     */
    @Pointcut("@annotation(com.example.framework.web.annotation.LogRequest) || @within(com.example.framework.web.annotation.LogRequest)")
    private void logRequest() {
    }

    /**
     * 前置通知
     */
    @Before("logRequest()")
    // 不能直接在方法签名中接收 LogRequest 参数。这是因为 @within 表达式用于指定切点，而不是直接获取注解实例。
    public void before(JoinPoint joinPoint) {
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 优先获取方法级别的注解
        LogRequest loggerAnnotation = method.getAnnotation(LogRequest.class);
        if (loggerAnnotation == null) {
            // 如果方法级别没有注解，则获取类级别的注解
            loggerAnnotation = method.getDeclaringClass().getAnnotation(LogRequest.class);
        }
        if (loggerAnnotation != null) {
            log.info("method: {}, source: {}, name: {}", method, loggerAnnotation.source(), loggerAnnotation.name());
        }

        Object[] args = joinPoint.getArgs();

        recordLog(signature, args);
    }

    private void recordLog(MethodSignature signature, Object[] args) {
        startTime.set(System.currentTimeMillis());
        String reqID = UUID.randomUUID().toString();
        try {
            reqUUID.set(reqID);
            // 接收到请求，记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            assert attributes != null;
            HttpServletRequest request = attributes.getRequest();
            // 记录下请求内容
            log.info("{} - URL : {}", reqID, request.getRequestURL().toString());
            log.info("{} - HTTP_METHOD : {},IP : {}", reqID, request.getMethod(), request.getRemoteAddr());
            Enumeration<String> e = request.getHeaderNames();
            while (e.hasMoreElements()) {
                String headerName = e.nextElement();
                Enumeration<String> headerValues = request.getHeaders(headerName);
                while (headerValues.hasMoreElements()) {
                    log.info("{} - Header - {} : {}", reqID, headerName, headerValues.nextElement());
                }
            }
            log.info("{} - Class_Method : {}", reqID, signature.getDeclaringTypeName() + "." + signature.getName());
            log.info("{} - Args_Name: {}", reqID, Arrays.toString(signature.getParameterNames()));
            log.info("{} - Args : {}", reqID, Arrays.toString(args));
        } catch (Exception e) {
            log.error("{} - LogRequestAspect Exception : {}", reqID, e.getMessage());
        }
    }

    /**
     * 后置通知
     */
    @AfterReturning(returning = "ret", pointcut = "logRequest()")
    public void doAfterReturning(Object ret) {
        // 处理完请求，返回内容
        if (ret instanceof R) {
            log.info("{} - RESPONSE : {}", reqUUID.get(), JsonUtils.toJsonString(ret));
        } else {
            log.info("{} - RESPONSE TYPE is Not R OR is NULL.", reqUUID.get());
        }
        log.info("{} - SPEND TIME : {}ms", reqUUID.get(), (System.currentTimeMillis() - startTime.get()));
    }

}
